《这样编码才规范 128个编码好习惯》

此书从早好几年前开始阅读,一直断断续续至今才看完。整本书个人感觉总体一般(实际可用性不高),但帮助本人重新思考了一下当前代码规范或软件工程需要注意的点。
个人觉得前1~2章可读,从软件工程角度强调编码规范的注意事项,第3章开始是具体的格式、api规范,很多规范不适用于当前主流,也可以通过工具解决,比如格式化可以用prettier。

1.基础知识概述

1.1 编码风格

代码编写过程:

  • 1.认清问题
  • 2.寻求解决问题的算法
  • 3.用流程图或伪代码描述算法
  • 4.用编程语言编写原始代码,此步骤也称为编码(coding)。编写代码的过程中需要处理以下几个部分,
    • 逻辑的表述方式
    • 算法的表述方式
    • 声明语句(statement)的布局方式
    • 表达式(expression)的表述方式
  • 5.编译源代码并生成目标代码
  • 6.进行单元测试并修正错误

编码和编程的含义几乎相同,但编程是指开发应用程序的逻辑层面的工作,编码可以理解为根据程序的逻辑、用特定的编程语言编写代码的工作。

其中编码过程中,程序员之间有约定速成的处理方法,统称为编码风格或编码方式。简言之,编写原始代码的过程=编码风格。

1.2 编码风格教育缺失

代码错误

  • 逻辑错误:存在逻辑方面的漏洞,特殊情况下会导致程序运行异常
  • 运算错误:特别是实数型运算时出现的误差不断积累,形成蝴蝶效应,造成运算上的严重误差
  • 调用错误:库提供的函数可能存在函数编写者也没有意识到的问题,导致调用此类函数的程序无法运行
  • 漏洞错误:函数调用、类的继承、组件之间的漏洞导致参数和返回的数据类型或数据值无法匹配
  • 环境错误:如果子啊不同于编写程序时假定的运行环境下运行,会出现意外的效果
  • 输入错误:输入程序员没有预料到的特殊值,导致运算结果出错
  • 运行错误:没有按照程序指定的顺序运行。如果是控制发电站等大规模工程的程序,则会导致严重问题

出现这些问题的原因多种多样,但最重要的是,开发人员没有经过充分训练,而且开发过程中没有遵循良好的编码习惯。

1.3 打磨编码风格的时机

趁早。

1
大学生 > 码农 > 初级程序员 > 高级程序员 > 首席程序员 > 系统架构师 > 系统分析师 > 项目经理

p-01.jpg

1.4 必须学习编码风格的原因

  • 1.为了缩短开发时间
  • 2.为了便于维护
  • 3.为了编写零漏洞的完美程序

所有软件研发项目中,开发成本占总成本的比例仅为20%,而维护成本占比高67%。 ——《人月神话》

p-02.jpg

维护工作主要由完善现有程序、修改错误等组成,即使开发新功能,通常也可以通过对现有程序进行变形而实现。因此只要现有程序是在严格遵守编码风格规则的基础上编写的,就可以大幅降低维护成本,进而提升开发公司的收益。

p-03.jpg

1.5 编码风格比数学或英语更重要

程序进行对话。

p-04.jpg

1.6 所有编程语言都需要编码风格

编程语言出现的时间不同,使用范围也各不相同,但适用于各自的编码风格规范却大同小异,因为所有编程语言都是将字符书写成语句的形态。
编码风格不受编程领域影响,编码风格适用于所有编程语言和业务领域。

1.7 选择用C语言阐述编程风格的原因

它得到了大范围的应用、可用于研究的源代码易于查找、它是各种语言的原型,可以最大程度保证程序员的自由。

p-05.jpg

  • C语言中可能出现的问题:
  • 1.除数为0会出现问题;
  • 2.使用空指针会出现问题;
  • 3.可再次访问已释放的动态分配内存;
  • 4.字符串末尾必须添加空字符;
  • 5.向超出数组范围的数组元素赋值会出现问题;
  • 6.数据类型不同的变量间可以进行运算;
  • 7.数据类型不同的指针访问内存会产生意想不到的结果;
  • 8.终止条件不明确导致无法检查递归函数;
  • 9.无法检查地址及其运算;
  • 10.使用指针变量前必须初始化。

C语言极有可能引发问题,所以更适合阐述编码风格。

1.8 编码风格有益于编译执行方式和混合执行方式

解释执行,如web;

混合执行方式是指,编译后生成的不是机器语言而是中间语言,其他部分与编译执行方式无异。

1.9 基于组件的软件开发方式与编码风格

基于组件的开发方式CBD(Component Based Development),这种方式采用面向对象的技术编写代码单元,每个代码单元就是组件,再将这些组件组装起来形成软件。

基于组件的开发方式能够提高效率,微软提出了包含组件设计原则(Principles of Component Design)在内的微软解决方案框架MSF(Microsoft Solutions Framework)

p-06.jpg

CBD的核心概念是将程序单元视为组件。

1.10 码农的力量不容小觑

码农负责编写整个系统中最基础的部分——代码,虽然与软件开发业务中的架构师工作相比,码农的工作或许看起来微不足道,但它实际占据着决定软件质量的核心地位。因此码农的作用不容忽视,码农编写的代码决定着整个系统的质量。

软件开发的核心应该是“代码!代码!代码”,只有代码质量达到了一定标准,软件质量才能令人满意。而保证代码质量的不是别人,正是程序员和码农。

1.11 将编码惯例文档化以统一应用

写代码和写文章之间有着千丝万缕的联系。写文章的人常常需要参考其他书目,比如讲些语法规则或者经典词句,这些类似于编程中的编码惯例。编程过程虽然没有全球统一的规则,但无论是编程语言创始人或大师们提出的管理,还是业内约定俗成的管理,都可以视为统一规则。

程序员也应该经常参考编程惯例,参考全球普遍认可的编码惯例。编程惯例就像一种惯用语句,如果不能与对方的语言和习惯形成共鸣,就很难理解其语言或代码,双方沟通会受到影响。

各公司应该收集各种编码惯例,从中选择与自身情况匹配的内容,并要求内部开发人员统一采纳。


2.程序设计相关 编码准则

2.1 遵循最新标准

行业标准日趋多样和精确。

2.2 合理限制开发人员的规模

并不是投入的开发人员越多,生产效率就越高。 ——《人月神话》

每个项目都应该适当限制开发人员数量,项目投入再多人力,也并不一定能显著提高生产效率。问题就在于沟通渠道n * (n - 1) / 2)。

沟通的实现程度称为“沟通强度”,也有人称为“团队精神”。公司规模越小,沟通渠道越少,沟通强度反而越高。

大公司参与开发的人员较多,一般采用正式的方式进行沟通,强调采用规范化的文书、规范化的方式和渠道,从而减慢了沟通的速度。因此虽然沟通渠道变多了但沟通强度反而变弱、导致整体沟通效率降低。

从“管理的极限”看,7人左右比较合适。

2.3 维护旧程序比开发新程序更常见

与编写程序的耗时相比,维护程序的耗时呈现逐渐增多的趋势,且这种趋势正在日渐加快。从这一层面看,程序员的工作既包含创新性业务,又包含功能型业务。其中,修改现有程序并对其进行维护的功能性工作较多。

制定编码风格需要坚持以“标注便于理解的注释,编写清晰简明的代码”为原则,明确规定各方面的详细规则。程序员则要遵循给定的编码规则,不断努力编写像小说或随笔一样具有较高可读性的代码,这也将有助于讲话后续对程序的修复、更新等维护工作。

2.4 不要认为修改程序很容易

实际上,重新编写程序确实比修改原有程序更快。

要限制需求变更。

2.5 慎重采用新技术

类似用惯了斧子伐木的人突然改用电锯,可能出现故障、误伤、效率下降等问题。当前,经过一段时间的磨合,掌握正确的电锯使用方法后,用电锯伐木的效率会明显高于用斧子的情况。

我们应当对不熟悉的事物保持敬畏之心。采用新技术时,不要忘记为学习新技术预留时间,如果项目工期紧需要在掌握新技术之前🔚,则应该果断放弃新技术直接采用熟悉的技术。

新方法得到广泛认可并能稳定应用之前,采用相对不浪费时间的技术才是比较正确的做法。编码风格宣扬的既不是激进主义也不是保守主义,而是改良主义。它主张在正常我们熟悉的语言的同时,对其稍加改进。长沙应用各种新的程序开发方法、编程语言、CASE(计算机辅助软件工程)工具前,应该优先考虑制定规范的编码风格,之后再投入使用并严格遵循。

2.6 不要采用RAF策略

p-07.jpg

RAF策略:Run and Fix,“先查看运行结果再修改程序”的工作方式。

欲速则不达。但仍然有很多程序员盲目相信压缩工期的魔力,认为快速完成任务才是第一要务。

程序是基于看不见的抽象逻辑编写的,而仅凭逻辑很难预测结果。

即使是小规模程序单元,RAF策略也会严重影响生产效率。

正确的做法:

  • 1.增加在程序构思阶段投入的时间;
  • 2.采用流程图和伪代码,充分梳理程序逻辑;
  • 3.在纸上投入的时间要多于在电脑上投入的时间;
  • 4.事先预测程序可能出现的问题,并寻找解决方法;
  • 5.深思熟虑后再编写代码。

3.间隔相关编码准则

3.1 一行只写一条语句

可读性。(Js-prettier可以处理)

其中的“行”是在编辑器上显示的行,而不是一条执行语句。

(Js-prettier可以处理)

3.2 区分声明语句和执行语句

可读性。

用空行区分声明语句和执行语句。不过注意插入太多空行同样会影响可读性。

3.3 区分段落

可读性。

执行语句之间也需要互相明确区分,当然,声明语句之间最好也能够明确区分。

编写程序时,无论是声明语句还是执行语句,都应该注意将相似的部分集中在一起,并在其前后插入空行,以便区分于其他语句。根据各语句负责的功能,将其划分为不同段落,这样有助于提高程序可读性,同时添加各段落主要功能的注释语句,更会起到锦上添花的作用。

3.4 区分各种控制语句

可读性。

表达统一思想的段落内部也应该插入空行,以划分更细的层次。最常见的情况是针对控制语句,如果某人没能正确理解程序中的控制语句,那么他极有可能误解整个程序的功能。

即使在段落内部,也应该在控制语句前后插入空行,以便快速识别。

p-08.jpg

switch 语句适用的段落划分准则:

  • 1.switch语句开始部分单独占据一行;
  • 2.以空行区分各case语句;
  • 3.像case语句一样,用空行隔开default语句,以示区分。

插入空行的方法有助于明确区分if、else、while、switch语句个字的控制范围。

3.5 区分各函数

可读性。

函数之间无空行的缺点:

  • 难以掌握函数的起始位置和结束位置;
  • 难以了解程序由几个函数组成;
  • 难以定位某个特定函数。

需要在各函数之间插入足够多的空行(可以考虑多行,保证间距够大)。

3.6 运算符前后留出空格

可读性。(Js-prettier可以处理)

在运算符前后各插入一个空格,使前后部分更加明确;分号后插入更多空格,可以使条件表达式、判断表达式、增减表达式更易区分。

不同情况下,适当插入一个或多个空格,可以使一条语句中连续使用的多个表达式层次更清晰。

3.7 不要在一元运算符与操作数之间插入空格

可读性。(Js-prettier可以处理)

++--这种只需要一个操作数的运算符称为一元运算符。

3.8 分号前不要插入空格

可读性。

3.9 不要滥用Tab键

可读性。(Js-prettier可以处理)

3.10 逗号后必须插入一个空格

可读性。(Js-prettier可以处理)

以更好地区分各参数。

3.11 逗号后不要插入太多空格

可读性。(Js-prettier可以处理)

一般1~2,基本1个够用。

3.12 变量初始化时的列对齐

可读性。(Js-prettier可以处理)

根据变量的用途,利用空行进行区分;还可以使用tab键,对齐各变量初始值所在列,但它可能会损害可读性,如:

p-09.jpg

应该在合适的范围内,即不能插入过多空格的前提下,对齐初始化值所在列。

p-10.jpg

通过空格的多少控制不同含义的变量初始值在不同位置对齐。

3.13 一行只声明一个变量

可读性。


4.编写缩进相关编码准则

4.1 大括号的位置

可读性。(Js-prettier可以处理)

第一种风格是,大括号始终与语句位于同一行。

第二种风格是,大括号和语句分占不同行。

第三种风格是,左边大括号与语句在同一行,右边大括号另起一行。


4.2 统一大括号的位置

可读性。(Js-prettier可以处理)

如果大括号不缩进,则很容易掌握函数结束的位置。

4.3 内部代码块需要缩进

可读性。(Js-prettier可以处理)

4.4 输出部分需要缩进

可读性。(Js-prettier可以处理)

1
2
if (xx)
console.log(xxx);

程序员选择不缩进的理由:

  • 全部精力集中于项目本身,对缩进并不关注;
  • 改写代码并将原始代码文件移植到其他系统平台的过程中,缩进不知不觉地消失了;

需要检查缩进的时间点:

  • 程序完结之前;
  • 与他人进行程序交接之前;
  • 文件改写完成之后。

4.5 不要毫无意义地缩进

可读性。(Js-prettier可以处理)

不要用毫无意义的缩进,如果只想表示强调,那么顽强可以用添加注释等方式代替缩进。

4.6 保持缩进程度的一致性

可读性。(Js-prettier可以处理)

缩进程度与嵌套程度并不相关,嵌套较深需要注释进行说明和区分。

4.7 选择合适的缩进程度

可读性。(Js-prettier可以处理)

4/2都可以,但不能过浅或过深。

4.8 不要缩写凸出形式的代码

可读性。(Js-prettier可以处理)

凸出形式:

1
2
3
  if (xxx) {
let a = 1; // 此行突出
}

凸出书写的代码和在稿纸边缘以外写字是一样的。


5.注释相关编码准则

5.1 多种注释形态

可读性。

程序专用编辑器不能实现黑体、加粗等样式效果,因此程序员设计了多种注释形态,以实现各类装饰效果

5.1.1 不包含强调内容的单行注释

最常见,没有需要特别强调的内容。

1
/* 单行注释常用于注释程序主体代码 */

5.1.2 包含强调内容的单行注释

1
/*--> 注意:需要处理错误信息 <--*/

或:

1
/*>>>>>>> 精密计算例程 <<<<<<<*/

如何装饰单行注释取决于程序员的个人喜好,无论使用何种特殊字符,只要它能够吸引眼球,并能明确标识注释的起止点即可。

5.1.3 不包含强调内容的多行注释

1
2
3
4
/*
* 编写包含多行语句的注释
* 没有需要特别强调的内容,但需要用较长的句子进行更准确的说明时
*/

可以插入特殊字符区分注释内容:

1
2
3
4
5
6
/*
* 编写包含多行语句的注释
* ----------------------------
* input1: 。。。
* input2: 。。。
*/

5.1.4 包含强调内容的多行注释

1
2
3
4
5
/****************************************/
/* !!!!!!!!!!! 警告(Warning) !!!!!!!!!! */
/****************************************/
/* 需要特别注意这段程序,,,,,,,,, */
/****************************************/

注释最重要的用途是引起程序员的注意,不要纠结于外观。

5.2 区分单行注释和注释框

可读性。

注释形式可大致分为:

  • 单行注释
  • 非单行注释(多行注释)

需要说明程序或函数时,应该采用多行注释。多行注释又称“注释框”或“块注释”。
注释框包含程序名、目标、编写者、修改者、编写日期和修改日期等众多信息,方便人们理解程序。除此之外程序员还可以增加任意信息。注释框相当于程序的参考文献,对于大规模项目尤为重要。

程序主体语句中不要使用注释框。任何人都能理解的内容,或代码中明确出现的内容都没有必要添加注释,更没有必要进行详细说明。如果实在想添加,那么单行注释足矣,而且此单行注释主要标注程序代码本身没有体现的信息。

5.3 添加“变量字典编写专用注释”

可读性。

但声明变量未必能让人理解变量具体信息,如单位。这时候可以在变量旁添加详细注释,这种方法称为“变量字典编写专用注释”,简称“字典注释”(diction comment):

1
2
3
let area;    // 面积:计算正在施工中的建筑物的地基面积。 
let wide; // 宽度:以米为单位,计算地基横向长度。
let height; // 高度:以米为单位,计算地基纵向长度。

字典注释不仅能够在程序内部描述各变量作用,还可以将各模块中变量声明的部分复制并保存到同一个文件,该文件即可视为能够说明所有变量的字典。这种字典在检查程序时非常重要,还可以避免变量命名冲突。

5.4 向程序插入伪代码

可读性。

注释的存在是为了使程序便于理解,即为了使包含程序编写者在内的所有人都能轻松理解程序的“目标和逻辑”。程序的“目标和逻辑”中,“逻辑”可以用伪代码补充。在程序起始部分用注释形式标注伪代码后,程序的整体脉络旧一目了然。

1
2
3
4
5
6
7
8
9
10
11
/**

计算两个数字变量num1、num2的最大值
[该程序伪代码]
如果 num1 大于 num2
则num1是最大值
返回num1
否则
返回num2
[伪代码结束]
*/

伪代码对于快速把握程序非常有效。

5.5 通过注释标注程序目标

可读性。

添加伪代码可以使程序逻辑一目了然。但有时候伪代码也并不短。这时候可以将程序的“目标”以注释的形式标注于程序最开始的部分:

/**

编写目标:计算两个数字变量num1、num2的最大值

[该程序伪代码]
如果 num1 大于 num2
则num1是最大值
返回num1
否则
返回num2
[伪代码结束]
*/

1
2
3
4
5
6
7
8
9
10
11
12

伪代码的主要作用是体现程序的“逻辑”,而不能完全展现“目标”。如果程序本身没有明确标注目标,那么人们阅读该程序时就不得不根据伪代码进行推测。

标注程序目标时,应该遵守“六何原则”:

``` js
/* 何时(when) 编写日期:2022年5月16日 */
/* 何地(where) 场 所:XX事业部XX组 */
/* 何人(who) 编写 者:XXX */
/* 何事(what) 代码性质:c语言代码,约200行 */
/* 为何(why) 编写理由:最大值最小值求值 */
/* 如何(how) 编写环境:控制台 */

我们应该在程序起始部分用注释形式添加伪代码和程序目标。

5.6 程序起始部分必须添加头注释

可读性。

注释可以分为“主体注释”和“头注释”两类,主体注释位于程序代码之间,用于说明对应部分的作用;头注释位于程序起始部分,用于指出程序的题目、作者、目标等。
头注释相当于名片,我们通过头注释可以首先对程序有整体的认知,再基于主体注释对其进行更细致的了解。

如:

1
2
3
4
5
6
7
8
9
10
/**
* 文件名:demo.ts
* 出处:blog.michealwayne.cn
* 编写者:MichealWayne
* 目标:读取用户登录记录,xxx。。。
* 提供方式:构建后的js
* 限制条件:1.nodejs 12.22以上
* 异常处理:1.出现各种错误时,应在xxx稳定上记录错误日志
* 历史记录:1.2022年5月16日,初始化,增加登录功能
*/

  • 文件名:记录程序或模块保存到内存时的源文件名。目标文件名或可执行文件名与源文件名不同时,需要同时记录。如果存在其他需要链接的目标代码,可以用+符号连接。
1
2
3
4
/**
* 文件名:源代码:demo.ts
* 目标代码:demo.js
*/
  • 编写者

  • 目标:记录为什么要编写这个程序,该程序负责处理什么任务,在整个系统中担任什么角色等。

  • 使用方式:方式、执行频率等。

  • 所需文件:记录需要处理的文件名、属性等,同时需要标注该文件释放可读、可写、可修改、可删除。

  • 限制条件:操作系统、CPU速度、程序大小、所需的硬盘容量等。

  • 异常处理:一定程度上预测并管理致命的异常。

  • 历史记录:修改时间、修改内容的核心、修改者。

5.7 在等于运算符旁添加注释

可读性。

1
2
3
for (let i = 1; i < 9; i++) {  // i从1开始计数
// ...
}

特别的,在条件语句中使用等于运算符或赋值运算符时,应尽可能在旁边添加注释。

5.8 在大括号闭合处添加注释

可读性。

通常,应该在大括号闭合处的旁边添加注释,说明该大括号表示哪部分结束。按照惯例,应该用end ifend mainend whileend class MyClass这种以end开头的注释

1
2
3
4
5
6
7
function demo () {
if (xxx) {

} // end if
} // end demo



5.9 在函数内部添加详细介绍函数的注释

可读性。

如果函数正上方或正下方添加了关于其作用和含义的详细注释,那么就不必逐条查看代码,也能提高效率。

应当记录:

  • 函数的目标;
  • 各参数允许的数据类型及含义;
  • 返回值应用于何处

5.10 注释标记原则

  • 代码本身足以说明问题时,不必添加注释;
  • 代码本身不足以说明问题时,尽可能添加详细注释。

6.标识符名称定义相关编码准则1

6.1 系统化定义变量名

可读性。

应该统一变量命名规则,系统化整理变量名可以避免混淆。

### 6.2 用匈牙利表示法命名变量

可读性。(不是特别推荐)

变量名以能表示——包含数据类型在内的——变量特性或功能的字符串为前缀。如下为整数型变量添加字符i作为前缀:

1
2
iNumber
iCounter

指针类型变量应添加前缀pptr

1
2
pMyPointer
ptrFilePointer

p-11.jpg

采用匈牙利表示法可以使变量的数据类型一目了然。但是使用匈牙利表示法需要记住每种数据类型并添加到变量名前缀,这非常繁琐。

6.3 用变量名前缀表示变量数据类型

可读性。(不是特别推荐)

p-12.jpg

6.4 用变量名前缀表示变量存储类型

可读性。(不是特别推荐)

p-13.jpg

存储类型指的是,考虑变量在内存中的生存期的长短、在程序代码中影响范围的大小,对变量进行分类。

还有用下划线区分变量的存储类型和数据类型:

1
const g_uc_mynum;

6.5 用函数名前缀表示函数功能

可读性。

  • avr:计算平均值,如avrOfTot
  • cnt:计算数据个数,如cntAllthing
  • check:检查某数值,如checkData
  • get:表示用于获取某数值
  • set:表示用于设置某数值
  • is:用于提出“是什么”的疑问
  • key:从数据中只获取关键字的值,如keyPaymentTable
  • max:表示获取最大
  • mid:表示获取中间值
  • min:表示获取最小值
  • put:表示用于存储

动词 + 宾语的形式。

6.6 编写个人专属前缀

可读性。

BreakSystem类创建了一个实例:bsNewBreak

需要注意的是,进行团队开发时,编写新的前缀需要告知团队中的所有人,或者在声明语句前添加相应注释。


标识符名称定义相关编码准则2

7.1 用有意义的名称命名

可读性。

标识符的名称一定要有意义。

7.2 不要使用相似的变量名

可读性。

比如通过变量名后加s以区分不同变量,如number和numbers,使用这种相似的变量名命名变量后,编程过程中容易混淆,应该用明显不同的单词命名,如number和digit。
不得不采用相似的变量名时,可以向原名称添加前缀或后缀。如day_yy、day_mm、day_dd。前缀有时比后缀更有效,比如iTotal和dTotal。

7.3 在不影响含义的前提下尽可能简短命名

可读性。

“短小精悍”。可以使用惯用缩写:

p-14.jpg

其他的还有:

  • Break -> brk
  • Control -> ctl
  • System -> sys

7.4 用下划线和大小写区分较长变量名

可读性。

甚至可以下划线和大小写都用。

7.5 变量名不要以下划线开始

可读性。

用下划线主要是为了避免冲突,但如果都推荐用下划线,反而就造成了命名冲突。

7.6 不要过度使用下划线

可读性。

只用一个下划线,不要用多个。

7.7 合理使用大小写命名标识符

可读性。

  • 变量和对象名以小写字母开始;
  • 函数、类、结构体、共用体等名称以大写字母开始(js函数一般不用);
  • 符号常量或宏函数的所有字母均大写。

7.8 不要滥用大小写区分1

可读性。

滥用1:

1
2
3
let num;
let Num;
let NUM;

7.9 不要滥用大小写区分2

可读性。

1
2
let iMyNumber = 10000;
let iMynumber = 10;

7.10 不能用相同名称同时命名类和变量

可读性。

7.11 用大写字母表示变量名中需要强调的部分

可读性。

1
localButIMPORTANT

8.运算符相关编码准则

8.1 恰当应用条件运算符有助于提高可读性

可读性。

如果if判断较多,有些情况下可以使用运算符缩减代码长度。

8.2 不要凭借运算符优先级排列算式

可读性。

需要添加括号,让人能够轻松理解。

8.3 指针运算符应该紧接变量名

可读性。(js倒不涉及)

1
2
int *a;
int b, c;

8.4 慎选移位运算,多用算术运算

可读性。

程序难以理解,也容易存在边界问题。

8.5 不要追求极端效率

要考虑可读性。


9.编写清晰代码所需编码准则

9.1 不要投机取巧,应致力于编写清晰易懂的程序

考虑可读性,也不要过分注释。

9.2 切忌混淆while语句中关系运算符和赋值运算符的优先级

1
2
3
while (c = (getChar() !== 1)) {
// ...
}

不可观,也容易失误。

9.3 不要进行隐式“非零测试”

1
2
3
while (getNum()) {
// ...
}

最好明确写出条件表达式。

9.4 不要在条件表达式中使用赋值语句

while/if/for/switch不要使用赋值语句。

9.5 避免产生副作用

比如++的取值等。几乎所有运算、控制、调用都可能引起副作用,遵循KISS原则编写程序。

9.6 函数原型中也要标注参数的数据类型

为了避免模糊性、强化明确性,必须在函数原型中也标记参数。

9.7 形式参数也需要命名

Val、num可能还不够语义化。

9.8 必须标注返回值的数据类型

(ts可以遵守这条规则)

9.9 留意结果值

各种运算、调用、控制返回值的最终结果的值。

9.10 在for语句等条件表达式中谨慎运算

可以将运算执行、方法提前。

9.11 大量使用冗余括号

可用可不用的括号有时能帮助理解,有助于区分关系运算符和逻辑运算符。

9.12 如果else语句使用大括号,那么if语句也应该使用

9.13 函数末尾务必编写return语句


10.编写可移植代码所需编码准则

10.1 文件名不超过14个字符

不同操作系统对文件名长度有不同限制,保证程序能够顺利移植到对文件名长度有所限制的系统。

10.2 不要在文件名中使用特殊字符

10.3 利用条件编译提高可移植性

Js不涉及

10.4 了解编译器的限制

如调用栈限制。

10.5 需考虑数据类型大小可能变化

10.6 不要指定绝对路径

10.7 可移植性和高效性二选一

p-15.jpg

提高效率的技术方法很大程序上都依赖系统,越是针对专门设备进行特殊化处理的程序,效率越高,也就是一定会牺牲可移植性。

10.8 用数组代替指针以提高可移植性

10.9 选择可移植性更好的编程语言

可移植性和可读性通常成正比。

10.10 不要插入低级语言编写的代码

汇编、机器语言。那么可移植性方面可能会出现问题。如果不得不用低级语言实现特定功能,则应该用相应语言编写库,事先编译,然后再以函数形式调用。


11.编写精确代码所需编码准则

可拓展性。

11.1计算机并不如想象得那么精确

数字电路机器无法准确表示小数。

11.2 需要进行准确计算时避开浮点数运算

浮点数的特性决定其无法进行精确运算,它一直存在误差,比如1/3。

11.3 double型比float型更适合精确计算

Doublue型运算速度比float要快(相差无几)。

要处理精度高的数值,可以用科学计算专用语言FORTRAN,或者用数组实现精度更高的实数运算。

11.4 确认整数型大小

大部分UNIX系列操作系统定义整数型大小为4字节(32位),PC常用操作系统定义整数型大小为2字节(16位),有的是4字节(32字节),事先最好确认整数型数据大小。

整数型大小也会影响程序的可移植性。

11.5 必须明确计算单位

最好在头注释和正文注释中全部记录计算单位,而且在变量声明部分和实际计算部分也再次标记计算单位。可以降低实际计算语句中误解单位的可能性,程序也会相应变得更加精确。

11.6 特别留意除法运算

要想正真理解除法运算,就要对编译器处理计算表达式的顺序烂熟于心。不同编译器对表达式的处理方法略有不同。

p-16.jpg

p-17.jpg

11.7 尽量避免数据类型转换

数据类型的转换始终会存在风险。将高类型(长字节)转换为低类型(短字节)时,会损失一定的数据精度。如将double型转换为float型。还要特别注意是否存在隐式数据类型转换。

一种做法是保证相关变量都声明为相同数据类型,还有一种方法是在变量名前添加可表示数据类型的前缀。

11.8 精通编程语言的语法

略显繁琐的语法往往能够决定整个程序。要想编写精确程序必须精通采用的编程语言的语法。

11.9 留意可能出现的非线形计算结果

计算机反复运算的结果可能远远偏离我们的预期。特别是涉及浮点数的话,这种现象尤为突出。

务必多次校验运算结果。


12.提升性能所需编码准则

可拓展性、稳定性。

12.1 重视性能,限制输出

最好支持用户可以自由设置程序运行时是否输出提示信息,也就是用户可以选择输出或不输出这些信息。

比如C语言中,负责格式化输出的printf()函数的运算成本要远高于其他运算。

12.2 用简单形式改写运算表达式

从C编译器处理运算层面来看,乘法运算成本高于加减法,除法运算成本又高于乘法,浮点数之间的运算成本高于整数型运算,成本最低的运算是位运算或者逻辑运算。

1
位运算、逻辑运算 < 加减法运算 < 乘法运算 < 除法运算 < 处理浮点数

尽可能选择成本较低的运算。

12.3 需要高效处理大文件时应使用二进制文件

用C语言处理文件时,一般使用ASCII格式的文件,优点在于在任何地方都可以查看该文件,因为任何计算机都提供可以查看ASCII格式文件的命令或程序。
但是ASCII文件的读写速度要低于二进制文件,而且所占空间更大。

因此如果程序性能至关重要或需要节约存储空间,最好选用二进制文件而非ASCII文件。

12.4 了解并使用压缩/未压缩结构体优缺点

压缩结构体:使用位域减少所占空间大小的结构体。
为了节约内存空间,信号处理领域多采用压缩结构体,但压缩结构体会减慢程序运行速度,因为位域运算需要消耗大量时间。相反,未压缩结构体虽然占据较多内存,但与压缩结构体相比,处理速度更快,而且不使用位域会使程序更容易理解、更加明确。

如果没有限制栈大小或栈足够大,或者并不需要很多信号处理变量,那么最好采用未压缩结构体;如果栈大小受限或需要很多信号处理变量,则最好使用压缩结构体。

12.5 根据运行环境选择编程语言

比如C/C++写的程序可以在操作系统中直接运行,速度比Java或C#编写的程序更快,但C/C++在网络环境下相对有些力不从心。

选择何种编程语言对程序的影响程度远大于缩进等细枝末节的影响。

12.6 根据情况选择手段

能够选择与业务情况、系统情况相吻合的恰当方法处理问题的程序员更为可贵。

能够从语言提供的各种工具中挑选最合适的,并知道如何充分利用该工具才最重要。

12.7 选择更优秀的数据结构

内存廉价且足够大的情况下,没有必要节约内存。有时需要处理的数据太多,超出数组可以承受的范围时应该选择数组以外的其他数据结构,比如链表。
应该根据情况选择合适的数据结构,程序员还可以用结构体、共用体和枚举定义并使用用户独有的数据结构,即用户自定义数据结构。

选用不同的数据结构决定了排序方式和排序性能。


13.编写易于理解的代码所需编码准则

可维护性。

13.1 不要使用goto语句

Goto语句更易上手和使用,但从程序易于理解和维护的角度来看,应该使用能够将逻辑块紧密联系的结构化程序设计方式,goto常常打乱程序逻辑。

13.2 不要替换C语言组成要素

汉字编程或自定义代码形态。

13.3 缩短过长数据类型名称

缩短过长的数据类型名,用更容易理解的名字重命名(与变量特性相吻合)。

13.4 使用if语句而非三元运算符

三元运算符有时更简洁,但容易导致代码难以理解。

13.5 数组维数应限制在三维之内

会导致难以理解和想象。

13.6 考虑驱动函数main函数的作用

Main函数是驱动函数,main函数调用的函数称为被动函数,main函数类似书的封面,应该记录各种程序信息以便日后其他人可以快速查找和更容易理解。

13.7 将常量替换为符号常量或const形态常量

1
2
3
4
5
6
7
8
// bad case
const sleepTime = 18 + 0.5;

// good case
const INITIAL_AIRCON_DEGREE = 18;
const AIRCON_DEGREE_STEP = 0.5;

const SLEEP_TIME = INITIAL_AIRCON_DEGREE + AIRCON_DEGREE_STEP;

13.8 考虑变量声明部分的顺序

顺序:

1
2
3
4
用户自定义数据类型
多个程序共用的外部变量
程序单元内部多个函数共用的全局变量
只有对应函数才会用到的局部变量

13.9 尽可能不使用全局变量

扰乱程序逻辑、增大理解难度。

13.10 遵循KISS原则

Keep it simple and short。

  • 易于修复代码漏洞(debugging)
  • 易于重构代码(refractoring)
  • 易于维护代码(maintenance)
  • 易于修复代码(rebuilding)
  • 易于复用代码(reusing)

14.用户接口处理相关编码准则

可拓展性。

14.1 确保保存输入值的变量足够大

与double相比应选择long double,与int型相比应用long int型声明变量。

14.2 转换说明符和参数个数应保持一致

printf()的使用

14.3 使用fgets()和sscanf()函数而非scanf()函数

scanf()对文件末尾出现的EOF的处理并不稳定,有时会遗漏EOF。

windows环境下,EOF对应的键盘输入值为^Z(Ctrl+Z),UNIX中对应的是^D(Ctrl+D)

14.4 使用fflush()函数清空标准输入/输出设备缓冲

可以将缓冲内容强制传递到输入/输出设备。


15.编写零漏洞代码所需编码准则

15.1 数组下标应从0开始

从1开始对数组进行计数引起的错误又称大小差一(off-by-one)错误。

15.2 置换字符串时必须使用括号

15.3 文件必须有开就有关

15.4 不要无视编译器的警告错误

  • 致命错误(fatal error)
  • 警告错误(warning error):也不能忽视

15.5 掌握并在编码时防止运行时错误

运行时错误不同于编译错误和逻辑错误,运行时错误与运行时环境紧密相关。

典型的运行时错误——栈溢出是由于操作系统限制栈的大小而产生的。最好在函数内部添加限制调用次数的龃龉。

还有一个典型的运行时错误就是除以0,特别是在循环中的“不小心”除以0。这需要细致检查程序所有可能的运行情况。

15.6 用静态变量声明大数组

p-18.jpg

除特别用extern、static、register声明的变量外,其他变量均为自动变量。自动变量存储于以栈形态管理数据的内存。如果一个程序用到的所有自动变量的综合超出栈的大小,就会触发栈溢出进而导致程序异常终止。

输入声明为静态变量,那么数组的存放位置就是堆,而不是栈,也就不会出现栈溢出。

15.7 预留足够大的存储空间

可以预留2~3倍甚至更多字符串存储空间。

15.8 注意信息交换引发的涌现效果

程序单元之间进行信息交换的过程中,可能出现信息丢失,也可能新增荣誉信息。

涌现性指的是能够引起意想不到的效果的性质,是复杂系统相关研究领域非常常见的词汇。
目前没有可行的对策但存在一种沿用至今的解决方式,即系统层面的综合测试方法。以严格的综合测试为基础,可以在一定程度上发现并预防涌现性现象,这要求在测试过程中投入域软件开发过程同样多的资源。


16.提升生产效率所需编码准则

可拓展性。

16.1 在对立关系中事先确定侧重于哪一方

对立关系指的是,选择两个策略性目标之一,则无法追求另一个目标的实现。比如使用位运算符有助于提高处理速度但同时会损坏系统安全。

这种对立关系中,侧重于哪一方面、牺牲哪一方面的选择在一定程度上与价值观有关。标准的做法是,应当事先规定在对立关系中选择侧重哪一方面。应该逐一列举处于对立关系的各个对象,从中选择更希望侧重的一个方面,并在项目流程中始终坚持自己的选择。

16.2 慎重采用最新工具

始终以挑剔的眼光看待工具,寻找更好的扩展应用、更好的开发工具。但需要注意,应用新工具时需要慎重,不要仓促地应用新的工具或方法论,因为它们有可能起到反作用。锋利的斧子固然有用,但首先要具备能够挥动斧子的力量。

16.3 记住所有标准库

要想使用某种编程语言,就应该牢记该编程语言提供的标准函数。

16.4 最大程度划分模块

积木。

设计系统或程序时,应该最大限度实现模块化,更小、更细的模块才更为重要。

16.5 明确区分术语

沟通顺畅才能有更高的生产效率,开发多人参与的项目前,应该先统一术语。

16.6 明确区分结构体、枚举体、共用体

p-19.jpg

16.7 明确区分概念

比如对象和类。

16.8 明确区分对象、类、实例

p-20.jpg